package pathfinding

import (
	"sync"

	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/serialize"
)

type JPSInterface interface {
	GetDirection(result *Vector2, x int16, y int16, dir byte)
	Direction(start *Vector2, end *Vector2) byte
	Distance(current *Vector2, goal *Vector2) float64
	DirectionCount() byte
	ExtendDir(sourceDir byte, currentDir byte) []byte
	ForceNeighbours(jps *JPS, grid JPSGrid, node *JPSNode, mask PathMask, dirs byte) (bool, byte)
	NaturalNeighbours(dir byte) byte
	GiveUpNeighbours(dir byte) byte
}

type JPSGrid interface {
	N(point *Vector2, mask PathMask) bool
	NodeCost(point *Vector2) int
	UpdateNode(point *Vector2, identity int, t byte)
	UpdateFixed(point *Vector2, identity int, t byte, view byte, fixed []byte, timestamp int64)
	CanAndLock(position *Vector2, timestamp int64, request PathPreSeat, step int) (bool, bool, serialize.IStruct)
}

var (
	jpsNodePool = sync.Pool{New: func() interface{} {
		return &JPSNode{Location: Vector2{}}
	}}

	jpsContainerPool = sync.Pool{New: func() interface{} {
		return &JPSContainer{
			NodeList: []*JPSNode{},
			Queue:    utils.NewNodeQueue(),
		}
	}}
)

type JPSContainer struct {
	JPS      *JPS
	NodeList []*JPSNode
	Queue    *utils.PriorityNodeQueue
}

func (container *JPSContainer) Release() {
	container.Queue.Clean()
	for _, node := range container.NodeList {
		//node.Revert()
		jpsNodePool.Put(node)
	}
	container.NodeList = container.NodeList[0:0]
}
func (container *JPSContainer) NewNode(x int16, y int16, dir byte) *JPSNode {
	node := jpsNodePool.Get().(*JPSNode)
	node.Location.X = x
	node.Location.Y = y
	node.Dir = dir
	node.Step = 0
	node.NodeCost = 0
	node.Distance = 0
	node.Step = 0
	node.Parent = nil
	node.Source = Vector2Slice{}
	container.NodeList = append(container.NodeList, node)
	return node
}

func (container *JPSContainer) NextNode(c *JPSNode, dir byte, start *Vector2, goal *Vector2) *JPSNode {
	node := container.NewNextNode(c.Location.X, c.Location.Y, dir, start, goal)
	node.Parent = c
	node.Step = c.Step + 1
	node.NodeCost = c.NodeCost
	return node
}

func (container *JPSContainer) NewNextNode(x int16, y int16, dir byte, start *Vector2, goal *Vector2) *JPSNode {
	a := Vector2Pool.Get().(*Vector2)
	container.JPS.Instance.GetDirection(a, x, y, dir)
	node := container.NewNode(x+a.X, y+a.Y, dir)
	Vector2Pool.Put(a)
	return node
}

type JPS struct {
	Instance JPSInterface
}

func NewJPS(instance JPSInterface) *JPS {
	jps := &JPS{
		Instance: instance,
	}
	return jps
}

func (jps *JPS) ENTERABLE(grid JPSGrid, node *JPSNode, dir byte, n byte, mask PathMask) bool {
	a := Vector2Pool.Get().(*Vector2)
	jps.Instance.GetDirection(a, node.Location.X, node.Location.Y, (dir+n)%jps.Instance.DirectionCount())
	v := node.Location
	v.X += a.X
	v.Y += a.Y
	r := grid.N(&v, mask)
	Vector2Pool.Put(a)
	return r
}

func (jps *JPS) FindPath(grid JPSGrid, start *Vector2, goal *Vector2, mask PathMask, complete bool, result []Vector2) (bool, []Vector2) {
	if !grid.N(goal, mask) {
		return false, result
	}
	if start.Equals(goal) {
		return true, result
	}
	container := jpsContainerPool.Get().(*JPSContainer)
	container.JPS = jps
	open := container.Queue
	openList := map[Vector2]*JPSNode{}
	closeList := map[Vector2]struct{}{}

	startNode := container.NewNode(start.X, start.Y, 255)
	open.Push(startNode, 0) //int(startNode.Cost(jps.Instance, &start, &goal))
	openList[startNode.Location] = startNode
	i := 0
	success := false
	//fmt.Println("goal", goal)
	for open.Len() > 0 {
		i++
		if i > 50 {
			break
		}
		n, _ := open.Pop()
		currentNode := n.(*JPSNode)
		//fmt.Println("pop", currentNode, currentNode.Cost(jps.Instance, &start, &goal))

		delete(openList, currentNode.Location)
		closeList[currentNode.Location] = struct{}{}

		dirs := jps.Instance.NaturalNeighbours(currentNode.Dir)
		_, dirs = jps.Instance.ForceNeighbours(jps, grid, currentNode, mask, dirs)
		for dir := byte(0); dir < jps.Instance.DirectionCount(); dir++ {
			if (1<<uint(dir))&dirs > 0 {
				nc := container.NextNode(currentNode, dir, start, goal)
				if _, ok := closeList[nc.Location]; ok {
					continue
				}
				index := jps.Jump(grid, container, nc, start, goal, currentNode.Dir, mask)
				if index == nil {
					continue
				}

				//fmt.Println(jps.Instance.Distance(&index.Location, &start) + jps.Instance.Distance(&index.Location, &goal))
				//fmt.Println("index:", index.Location, index.Step, index.Parent, ",dir:"+utils.ToString(index.Dir))
				if index.Location.Equals(goal) {
					result = jps.Trace(index, complete, result)
					success = true
					goto Release
				}
				cost := index.Cost(grid, jps.Instance, start, goal)
				if v, ok := openList[index.Location]; !ok {
					//fmt.Println("add", index.Location, cost, dir)
					open.Push(index, int(cost))
					openList[index.Location] = index
				} else if v.Cost(grid, jps.Instance, start, goal) > cost {
					v.Update(jps, index)
					open.Fix(v, int(cost))
				}
			}
		}
	}
Release:
	container.Release()
	jpsContainerPool.Put(container)
	return success, result

}

func (jps *JPS) Jump(grid JPSGrid, container *JPSContainer, nc *JPSNode, start *Vector2, goal *Vector2, dir byte, mask PathMask) *JPSNode {
	extendDir := jps.Instance.ExtendDir(dir, nc.Dir)
	var dirs byte
	var ok bool
	//fmt.Println(nc)

	if !grid.N(&nc.Location, mask) {
		return nil
	}
	cost := grid.NodeCost(&nc.Location)
	nc.NodeCost += cost
	for {
		//fmt.Println("jump", nc.Location, dir, nc.Dir)
		ok, _ = jps.Instance.ForceNeighbours(jps, grid, nc, mask, dirs)
		//fmt.Println(ok, nc.Location, nc.Dir, jps.Instance.GetDirection(nc.Location.X, nc.Location.Y, nc.Dir))
		if ok || nc.Location.Equals(goal) {
			//fmt.Println(nc.Location, nc.Source)
			return nc
		}
		for _, d := range extendDir {
			if jps.GetJumpPoint(grid, container, nc, start, goal, d, mask) {
				return nc
			}
		}
		offset := Vector2Pool.Get().(*Vector2)
		jps.Instance.GetDirection(offset, nc.Location.X, nc.Location.Y, nc.Dir)
		nc.Offset(offset)
		Vector2Pool.Put(offset)

		if !grid.N(&nc.Location, mask) {
			return nil
		}
		// 确保不同消耗路径加入跳点：进入选择排序
		//newCost := grid.NodeCost(&nc.Location)
		//if cost != newCost {
		//	nc.ReOffst(offset)
		//	return nc
		//}
		//nc.NodeCost += newCost
	}
}

func (jps *JPS) GetJumpPoint(grid JPSGrid, container *JPSContainer, node *JPSNode, start *Vector2, goal *Vector2, dir byte, mask PathMask) bool {
	r := container.NewNextNode(node.Location.X, node.Location.Y, dir, start, goal)
	dirs := byte(0)
	var ok bool
	for {
		//fmt.Println(r.Location, dir)
		if !grid.N(&r.Location, mask) {
			//r.Revert(jps)
			return false
		}
		ok, dirs = jps.Instance.ForceNeighbours(jps, grid, r, mask, dirs)
		if ok || r.Location.Equals(goal) {
			//r.Revert(jps)
			return true
		}
		a := Vector2Pool.Get().(*Vector2)
		jps.Instance.GetDirection(a, r.Location.X, r.Location.Y, dir)
		r.Offset(a)
		Vector2Pool.Put(a)
	}
}

func (jps *JPS) FindEmpty(grid JPSGrid, start *Vector2, goal *Vector2, timestamp int64, request PathPreSeat, result []Vector2) (bool, []Vector2) {
	container := jpsContainerPool.Get().(*JPSContainer)
	container.JPS = jps
	open := container.Queue
	openList := map[Vector2]*JPSNode{}
	closeList := map[Vector2]struct{}{}

	startNode := container.NewNode(start.X, start.Y, 255)
	open.Push(startNode, 0)
	openList[startNode.Location] = startNode

	if goal == nil {
		goal = start
	}
	//fmt.Println("goal", goal)
	success := false
	for open.Len() > 0 {
		n, _ := open.Pop()
		currentNode := n.(*JPSNode)
		//fmt.Println("pop", currentNode, currentNode.Cost(jps.Instance, &start, &goal))

		delete(openList, currentNode.Location)
		closeList[currentNode.Location] = struct{}{}
		dirs := jps.Instance.NaturalNeighbours(currentNode.Dir)
		for dir := byte(0); dir < jps.Instance.DirectionCount(); dir++ {
			if (1<<uint(dir))&dirs > 0 {
				nc := container.NextNode(currentNode, dir, start, start)
				if _, ok := closeList[nc.Location]; ok {
					continue
				}
				can, lock, _ := grid.CanAndLock(&nc.Location, timestamp, request, nc.Step)
				//fmt.Println("empty", start, dir, currentNode, can, lock)
				// 找寻是否可以移动
				if !can {
					continue
				}
				// 是否可以停留并锁定
				if lock {
					result = jps.Trace(nc, true, result)
					success = true
					goto Release
				}

				cost := nc.Cost(grid, jps.Instance, start, goal)
				if _, ok := openList[nc.Location]; !ok {
					//fmt.Println("add", index.Location, cost, dir)
					open.Push(nc, int(cost))
					openList[nc.Location] = nc
				}
			}
		}
	}
Release:
	container.Release()
	jpsContainerPool.Put(container)
	return success, result
}
func (jps *JPS) Trace(node *JPSNode, complete bool, result []Vector2) []Vector2 {
	for node.Parent != nil {
		//fmt.Println("trace", node.Location, node.Source)
		var tmp = node.Parent
		result = append(result, node.Location)
		if complete && len(node.Source) > 0 {
			for i := len(node.Source) - 1; i >= 0; i-- {
				result = append(result, node.Source[i])
			}
		}
		node = tmp
	}
	result = append(result, node.Location)
	for i := 0; i < len(result)/2; i++ {
		j := len(result) - i - 1
		(result)[i], (result)[j] = (result)[j], (result)[i]
	}
	return result
}

type JPSNode struct {
	Location Vector2
	Dir      byte
	Distance float64
	Step     int
	Parent   *JPSNode
	Source   Vector2Slice
	NodeCost int
}

func NewJPSNode() *JPSNode {
	node := &JPSNode{
		Location: Vector2{},
		Dir:      0,
		Distance: 0,
		Step:     0,
		Parent:   nil,
		Source:   Vector2Slice{},
	}
	return node
}

func (node *JPSNode) Cost(grid JPSGrid, jps JPSInterface, start *Vector2, goal *Vector2) float64 {
	return jps.Distance(&node.Location, goal) + float64(node.Step+node.NodeCost)
}

func (node *JPSNode) Revert() {
	node.Distance = 0
	node.Step = 0
	node.Dir = 255
	node.Parent = nil
	node.Source = Vector2Slice{}
	jpsNodePool.Put(node)
}

func (node *JPSNode) Offset(offset *Vector2) {
	node.Source = append(node.Source, node.Location)
	node.Location.X += offset.X
	node.Location.Y += offset.Y
	//node.Step += 1
}

func (node *JPSNode) ReOffst(offset Vector2) {
	node.Source = node.Source[0 : len(node.Source)-2]
	node.Location.X -= offset.X
	node.Location.Y -= offset.Y
}

func (node *JPSNode) Update(jps *JPS, other *JPSNode) {
	node.Step = other.Step
	node.Distance = other.Distance
	node.Source = other.Source[:]
	node.Parent = other.Parent
}

func (node *JPSNode) Equal(other interface{}) bool {
	return node.Location.Equals(&other.(*JPSNode).Location)
}
